#ifndef XRPL_BASICS_SAFE_CAST_H_INCLUDED
#define XRPL_BASICS_SAFE_CAST_H_INCLUDED

#include <type_traits>

namespace ripple {

// safe_cast adds compile-time checks to a static_cast to ensure that
// the destination can hold all values of the source.  This is particularly
// handy when the source or destination is an enumeration type.

template <class Src, class Dest>
concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
    (std::is_signed<Src>::value || std::is_unsigned<Dest>::value) &&
    (std::is_signed<Src>::value != std::is_signed<Dest>::value
         ? sizeof(Dest) > sizeof(Src)
         : sizeof(Dest) >= sizeof(Src));

template <class Dest, class Src>
inline constexpr std::
    enable_if_t<std::is_integral_v<Dest> && std::is_integral_v<Src>, Dest>
    safe_cast(Src s) noexcept
{
    static_assert(
        std::is_signed_v<Dest> || std::is_unsigned_v<Src>,
        "Cannot cast signed to unsigned");
    constexpr unsigned not_same =
        std::is_signed_v<Dest> != std::is_signed_v<Src>;
    static_assert(
        sizeof(Dest) >= sizeof(Src) + not_same,
        "Destination is too small to hold all values of source");
    return static_cast<Dest>(s);
}

template <class Dest, class Src>
inline constexpr std::
    enable_if_t<std::is_enum_v<Dest> && std::is_integral_v<Src>, Dest>
    safe_cast(Src s) noexcept
{
    return static_cast<Dest>(safe_cast<std::underlying_type_t<Dest>>(s));
}

template <class Dest, class Src>
inline constexpr std::
    enable_if_t<std::is_integral_v<Dest> && std::is_enum_v<Src>, Dest>
    safe_cast(Src s) noexcept
{
    return safe_cast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}

// unsafe_cast explicitly flags a static_cast as not necessarily able to hold
// all values of the source. It includes a compile-time check so that if
// underlying types become safe, it can be converted to a safe_cast.

template <class Dest, class Src>
inline constexpr std::
    enable_if_t<std::is_integral_v<Dest> && std::is_integral_v<Src>, Dest>
    unsafe_cast(Src s) noexcept
{
    static_assert(
        !SafeToCast<Src, Dest>,
        "Only unsafe if casting signed to unsigned or "
        "destination is too small");
    return static_cast<Dest>(s);
}

template <class Dest, class Src>
inline constexpr std::
    enable_if_t<std::is_enum_v<Dest> && std::is_integral_v<Src>, Dest>
    unsafe_cast(Src s) noexcept
{
    return static_cast<Dest>(unsafe_cast<std::underlying_type_t<Dest>>(s));
}

template <class Dest, class Src>
inline constexpr std::
    enable_if_t<std::is_integral_v<Dest> && std::is_enum_v<Src>, Dest>
    unsafe_cast(Src s) noexcept
{
    return unsafe_cast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}

}  // namespace ripple

#endif
